package com.imyuanma.qingyun.common.client.monitor.trace;

import com.imyuanma.qingyun.common.client.ums.LoginUserHolder;
import com.imyuanma.qingyun.common.exception.Exceptions;
import com.imyuanma.qingyun.common.util.*;
import com.imyuanma.qingyun.interfaces.common.model.enums.EYesOrNoEnum;
import com.imyuanma.qingyun.interfaces.monitor.annotation.Trace;
import com.imyuanma.qingyun.interfaces.monitor.model.TraceDTO;
import com.imyuanma.qingyun.interfaces.ums.model.LoginUserDTO;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.stream.Collectors;

/**
 * 链路元数据
 *
 * @author wangjy
 * @date 2022/09/16 22:25:57
 */
public class TraceMeta {
    private static final Logger logger = LoggerFactory.getLogger(TraceMeta.class);
    /**
     * 链路数据
     */
    private TraceDTO traceDTO;
    /**
     * 方法链路注解上的标签
     */
    private String[] tags;
    /**
     * 方法返回结果
     */
    private Object result;
    /**
     * 方法抛出的异常
     */
    private Throwable throwable;

    private TraceMeta() {
        this.traceDTO = new TraceDTO();
        this.traceDTO.setUniqueKey(StringUtil.getUUID());
    }

    /**
     * 构造链路数据
     *
     * @param joinPoint
     * @return
     */
    public static TraceMeta start(JoinPoint joinPoint) {
        try {
            TraceMeta traceMeta = new TraceMeta();
            // 获取目标方法的标记
            MethodSignature ms = (MethodSignature) joinPoint.getSignature();
            // 目标方法
            Method method = ms.getMethod();
            // 注解信息
            Trace trace = method.getAnnotation(Trace.class);
            // 目标类
            Class clz = joinPoint.getTarget().getClass();

            // 处理基础信息, 链路信息, 执行信息, 处理web信息
            traceMeta.start4Base(clz, method, trace)
                    .start4Trace()
                    .start4Running(joinPoint, clz, method)
                    .start4Web(clz);
            return traceMeta;
        } catch (Throwable t) {
            logger.error("[链路节点开始] 构造链路项数据异常", t);
        }
        return null;
    }

    /**
     * 链路节点结束数据填充
     *
     * @param result
     * @param throwable
     */
    public void end(Object result, Throwable throwable) {
        try {
            this.result = result;
            this.throwable = throwable;
            traceDTO.setEndTime(System.currentTimeMillis());
            if (traceDTO.getBeginTime() != null && traceDTO.getEndTime() != null) {
                traceDTO.setMs((int) (traceDTO.getEndTime() - traceDTO.getBeginTime()));
            }
            traceDTO.setHasEx(throwable != null ? EYesOrNoEnum.YES.getCode() : EYesOrNoEnum.NO.getCode());
            traceDTO.setEx(throwable != null ? Exceptions.exceptionCauseMessage(throwable) : null);
            traceDTO.setOutParam(JsonUtil.toJson(result));

            // 结束时检查业务id, 支持在方法执行时设置业务id
            if (StringUtil.isNotBlank(TraceContext.getBusinessId())) {
                traceDTO.setBusinessId(TraceContext.getBusinessId());
            }

            // 保存链路数据
            TraceDataRepository.offer(this);
        } catch (Throwable t) {
            logger.error("[链路节点结束] 链路节点结束数据填充异常", t);
        }
    }

    /**
     * web参数
     *
     * @param clz
     */
    private void start4Web(Class clz) {
        if (isWebApi(clz)) {
            HttpServletRequest request = WebUtil.getCurrentHttpServletRequest();
            if (request != null) {
                LoginUserDTO loginUser = LoginUserHolder.getLoginUser();
                if (loginUser != null) {
                    traceDTO.setUserId(String.valueOf(loginUser.getUserId()));
                    traceDTO.setUserName(loginUser.getName());
                }
                traceDTO.setUserIp(ClientInfoUtil.getClientIpAddr(request));//ip地址
                traceDTO.setCip(traceDTO.getUserIp());// web请求, 从请求头获取调用方ip
                traceDTO.setOs(ClientInfoUtil.getClientOS(request));//操作系统
                traceDTO.setBrowser(ClientInfoUtil.getClientBrowser(request));//浏览器信息
                traceDTO.setScheme(request.getScheme());//协议,http/https
                traceDTO.setHost(request.getServerName());//host
                traceDTO.setPort(request.getServerPort());//端口号
                traceDTO.setUri(request.getRequestURI());//uri
                traceDTO.setUrl(request.getRequestURL().toString());//请求地址
                traceDTO.setMethod(request.getMethod());//请求方式
                traceDTO.setReferer(ClientInfoUtil.getReferer(request));//Referer
                traceDTO.setOrigin(ClientInfoUtil.getOrigin(request));//Origin
            }
        }
    }

    /**
     * 基础参数
     *
     * @param clz
     * @param method
     * @param trace
     * @return
     */
    private TraceMeta start4Base(Class clz, Method method, Trace trace) {
        traceDTO.setAppName(StringUtil.getDefaultValue(trace.appName(), "default"));
        traceDTO.setKeyCode(StringUtil.getDefaultValue(trace.key(), clz.getName() + "." + method.getName()));
        traceDTO.setKeyInfo(trace.value());
        tags = trace.tags();
        return this;
    }

    /**
     * 链路参数
     *
     * @return
     */
    private TraceMeta start4Trace() {
        traceDTO.setTraceId(TraceContext.getTraceId());
        traceDTO.setBusinessId(TraceContext.getBusinessId());
        traceDTO.setParentId(TraceContext.getParentSpanId());
        traceDTO.setSpanId(TraceContext.getSpanId());
//        traceDTO.setCip();
        traceDTO.setSip(IPUtil.getLocalIp());
//        traceDTO.setCs();
//        traceDTO.setSr();
//        traceDTO.setSs();
//        traceDTO.setCr();
        return this;
    }

    /**
     * 执行参数
     *
     * @param joinPoint
     * @param clz
     * @param method
     * @return
     */
    private TraceMeta start4Running(JoinPoint joinPoint, Class clz, Method method) {
        traceDTO.setClassName(clz.getName());
        traceDTO.setMethodName(method.getName());
        traceDTO.setBeginTime(System.currentTimeMillis());
        Object[] args = joinPoint.getArgs();
        if (CollectionUtil.isNotEmpty(args)) {
            traceDTO.setInParam(JsonUtil.toJson(Arrays.stream(args).filter(TraceMeta::can2Json).collect(Collectors.toList())));
        }
        traceDTO.setServerIp(IPUtil.getLocalIp());
        return this;
    }

    /**
     * 是否web接口
     *
     * @param clz
     * @return
     */
    private static boolean isWebApi(Class clz) {
        return clz.getAnnotation(Controller.class) != null || clz.getAnnotation(RestController.class) != null;
    }

    /**
     * 是否可以转json
     *
     * @param obj
     * @return
     */
    private static boolean can2Json(Object obj) {
        return !(obj instanceof ServletRequest || obj instanceof ServletResponse || obj instanceof MultipartFile);
    }


    public TraceDTO getTraceDTO() {
        return traceDTO;
    }

    public String[] getTags() {
        return tags;
    }

    public Object getResult() {
        return result;
    }

    public Throwable getThrowable() {
        return throwable;
    }
}
